20230817-105949
Python中类的特殊方法
Builtin Function type
type本身是可以当成一个内置的函数来使用,传入一个参数然后返回这个参数的类型。但是,type本身也可以当作一个类的构造器,此时需要传入3个参数(这里也可以说是根据传入参数个数的不同,type有不同的用法)。传入的三个参数为:
- name: name of the class
- bases: tuple of the parent class (for inheritance, can be empty)
- attrs: dictionary containing attributes names and values
# <type 'int'>
type(3.0)
# <__main__.MyClass object at 0x >
MyClass = type('MyClass', bases, attrs)
元类
通过上面type方法,我们可以手动的创建一个类,事实上我们常用的class MyClass: 的声明方法,其后台就是采用type来构建类。这里可以引出一个概念---元类,也就是定义如何创建类的类。而在没有特殊指定的情况下,是默认使用metaclass=type来进行类的构建。因此,我们也可以在生成类时使用自定义的元类,以满足各种需求,像单例模式、方法检查等。下面给出一种实现自动注册机制的元类示意(在类构建过程增加对基类方法有无的判断,进而找到那些继承ModelBase的子类,然后在构建类过程中,调用注册方法将其储存在_models字典中):
class AutoRegisterMetaclass(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 类的构建是通过__new__方法,其和__init__、__call__特殊方法的区别在下面进行展开
def __new__(cls, name, bases, attrs):
cls = super().__new__(cls, name, bases, attrs)
for base in bases:
if hasattr(base, '_NNModel__registerSubclass'):
base._NNModel__registerSubclass(cls)
return cls
class ModelBase(metaclass=AutoRegisterMetaclass):
_models = {}
def __init__(self):
pass
@classmethod
def __registerSubclass(cls, klass):
cls._models[klass.__repr__(klass)] = klass
class ModelA(ModelBase):
def __init__(self):
pass
辨析特殊方法
至此,我们已经对元类有一个初步的认识,它可以让我们在类生成前,对类的生成进行定制。下图是一个类的处理流程,需要注意的有以下几点:
- 对于__new__方法,类比的说,他是定义了如何生成class,但是并不设计class的实例化。也就是当我们使用import MyClass时,会调用__new__方法,来检查类实例的生成。
- 而__new__方法的返回会在后续初始化实例过程中,作为第一个参数传入__init__方法,进而生成一个实例。
- 对于另一个倒霉蛋__call__则是拿来凑数字的,其是在初始化实例后,call的时候才被执行。
graph LR;
subgraph import
id1[main] ---> id2[class MyClass] ---> id3{custom
metaclass ?};
id3 ---> id4[__new__] ---> id5[class instance];
end
subgraph initialize
id6["MyClass()"] ---> id7[__init__] ---> id8[instance];
end
id5 ---> id6和装饰器的执行顺序
Python中的装饰器也可以实现在代码执行前进行修饰的功能,但是其和元类还是有明显的功能差距的,个人感觉元类更加面向对象,可以通过继承关系传递给子类;而装饰器可能更灵活些,把一些额外的功能独立出来。把两者放在一起又能碰撞出怎样的火花呢😶🌫️?
通过[[#辨析特殊方法]]中对元类中__new__和__init__特殊方法执行的流程进行分析,可以得出元类和类实例的生成要早于装饰器代码的执行(其实也很好理解,因为根据装饰器的语法糖func = decorator(func),肯定要先有fun😂)。
还有一点是在装饰器类本身,同样是根据装饰器语法,可以才想出**类实例生成的返回值(new)**会被传递给被修饰对象,因此有以下几种实现装饰器类的方法:
- 修改__new__:通过把装饰器的__new__方法进行修改(第一个参数是装饰器类自己本身,第二个则是被修饰的类),此时__new__的return就必须是被修饰类,因为这个返回值要返回给被修饰对象。
- 单独staticmethod方法:这个就有点像使用函数来当修饰器了
- 修改__call__方法:此时需要完成类的初始化,注意装饰器类__init__参数的传递。而且在装饰器写的时候需要使用
@Decorator()来首先完成装饰器类的实例化。
下面给出目前在项目中,一种装饰器类的实现代码(其实就是通过修改__new__方法实现的):
class Decorator():
def __init__(self, *args) -> None:
print("Decorator.init")
for i in args:
print(f"arg: {i}")
def __new__(cls, klass):
print(f"Decorator.__new__ {cls} {klass}")
klass.__init__ = cls.__init__
return klass
def __call__(self, *args, **kwds):
print(f"Decorator.__call__ {args} {kwds}")
class MetaclassTest(type):
def __new__(cls, name, bases, attrs):
print(f"MetaclassTest.__new__ {cls} {name}")
print("----end")
return super().__new__(cls, name, bases, attrs)
@Decorator
class A(metaclass=MetaclassTest):
def __init__(self) -> None:
print("in A")
'''
输出为:
MetaclassTest.__new__ <class '__main__.MetaclassTest'> A
----end
Decorator.__new__ <class '__main__.Decorator'> <class '__main__.A'>
'''